﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;


namespace advadev.Windows.Graph
{

    /// <summary>
    /// Represents a graph segment container
    /// </summary>
    internal class GraphSegmentContainer : Control
    {

        /// <summary>for internal use</summary>
        private EventHandler _onGraphNodeLayerIndexChangedEventHandler;
        /// <summary>for internal use</summary>
        private EventHandler _onIsLayerWithComplexGeometryEventHandler;
        /// <summary>for internal use</summary>
        private EventHandler _onStartNodeAlignmentChangedEventHandler;
        /// <summary>for internal use</summary>
        private EventHandler _onEndNodeAlignmentChangedEventHandler;
        /// <summary>for internal use</summary>
        private EventHandler _onStartNodeDirectionChangedEventHandler;
        /// <summary>for internal use</summary>
        private List<UIElement> _layerElements = new List<UIElement>();


        /// <summary>
        /// Initializes the GraphSegmentContainer class
        /// </summary>
        static GraphSegmentContainer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(GraphSegmentContainer), new FrameworkPropertyMetadata(typeof(GraphSegmentContainer)));
        }

        /// <summary>
        /// Initializes a new instance of the GraphSegmentContainer class
        /// </summary>
        internal GraphSegmentContainer()
        {
            _onGraphNodeLayerIndexChangedEventHandler = new EventHandler((object sender, EventArgs args) =>
            {
                updateRelevantLayerElementsList();
            });
            _onIsLayerWithComplexGeometryEventHandler = new EventHandler((object sender, EventArgs args) =>
            {
                updateRelevantLayerElementsList();
                updateGeometryClip();
            });
            _onStartNodeAlignmentChangedEventHandler = new EventHandler((object sender, EventArgs args) =>
            {
                updateStartPoint();
            });
            _onEndNodeAlignmentChangedEventHandler = new EventHandler((object sender, EventArgs args) =>
            {
                updateEndPoint();
            });
            _onStartNodeDirectionChangedEventHandler = new EventHandler((object sender, EventArgs args) =>
            {
                invalidateVisual();
            });
            RelevantLayerElements = new Collection<UIElement>();
            RelevantNotComplexLayerElements = new Collection<UIElement>();
            RelevantComplexLayerElements = new ObservableCollection<LayerElementContainer>();
        }


        #region dependency properties
        static internal readonly DependencyProperty ParentGraphProperty = DependencyProperty.Register("ParentGraph", typeof(Graph), typeof(GraphSegmentContainer), new UIPropertyMetadata(null));
        internal Graph ParentGraph
        {
            get { return (Graph)GetValue(ParentGraphProperty); }
            set { SetValue(ParentGraphProperty, value); }
        }

        static internal readonly DependencyProperty StartElementProperty = DependencyProperty.Register("StartElement", typeof(UIElement), typeof(GraphSegmentContainer), new UIPropertyMetadata(null, onStartElementChanged));
        internal UIElement StartElement
        {
            get { return (UIElement)GetValue(StartElementProperty); }
            set { SetValue(StartElementProperty, value); }
        }

        static internal readonly DependencyProperty EndElementProperty = DependencyProperty.Register("EndElement", typeof(UIElement), typeof(GraphSegmentContainer), new UIPropertyMetadata(null, onEndElementChanged));
        internal UIElement EndElement
        {
            get { return (UIElement)GetValue(EndElementProperty); }
            set { SetValue(EndElementProperty, value); }
        }

        static internal readonly DependencyProperty StartPointProperty = DependencyProperty.Register("StartPoint", typeof(Point), typeof(GraphSegmentContainer), new UIPropertyMetadata(new Point(0d, 0d)));
        internal Point StartPoint
        {
            get { return (Point)GetValue(StartPointProperty); }
            set { SetValue(StartPointProperty, value); }
        }

        static internal readonly DependencyProperty EndPointProperty = DependencyProperty.Register("EndPoint", typeof(Point), typeof(GraphSegmentContainer), new UIPropertyMetadata(new Point(0d, 0d)));
        internal Point EndPoint
        {
            get { return (Point)GetValue(EndPointProperty); }
            set { SetValue(EndPointProperty, value); }
        }

        static internal readonly DependencyProperty ClipGeometryProperty = DependencyProperty.Register("ClipGeometry", typeof(Geometry), typeof(GraphSegmentContainer), new UIPropertyMetadata(null));
        internal Geometry ClipGeometry
        {
            get { return (Geometry)GetValue(ClipGeometryProperty); }
            set { SetValue(ClipGeometryProperty, value); }
        }

        static internal readonly DependencyProperty RelevantLayerElementsProperty = DependencyProperty.Register("RelevantLayerElements", typeof(Collection<UIElement>), typeof(GraphSegmentContainer), new UIPropertyMetadata(null));
        internal Collection<UIElement> RelevantLayerElements
        {
            get { return (Collection<UIElement>)GetValue(RelevantLayerElementsProperty); }
            set { SetValue(RelevantLayerElementsProperty, value); }
        }

        static internal readonly DependencyProperty RelevantNotComplexLayerElementsProperty = DependencyProperty.Register("RelevantNotComplexLayerElements", typeof(Collection<UIElement>), typeof(GraphSegmentContainer), new UIPropertyMetadata(null));
        internal Collection<UIElement> RelevantNotComplexLayerElements
        {
            get { return (Collection<UIElement>)GetValue(RelevantNotComplexLayerElementsProperty); }
            set { SetValue(RelevantNotComplexLayerElementsProperty, value); }
        }

        static internal readonly DependencyProperty RelevantComplexLayerElementsProperty = DependencyProperty.Register("RelevantComplexLayerElements", typeof(ObservableCollection<LayerElementContainer>), typeof(GraphSegmentContainer), new UIPropertyMetadata(null));
        internal ObservableCollection<LayerElementContainer> RelevantComplexLayerElements
        {
            get { return (ObservableCollection<LayerElementContainer>)GetValue(RelevantComplexLayerElementsProperty); }
            set { SetValue(RelevantComplexLayerElementsProperty, value); }
        }
        #endregion dependency properties


        /// <summary>
        /// Adds the given layer element
        /// </summary>
        /// <param name="layerElement">
        /// Layer element
        /// </param>
        internal void AddLayerElement(UIElement layerElement)
        {
            if (!_layerElements.Contains(layerElement))
            {
                _layerElements.Add(layerElement);
                layerElement.IsVisibleChanged += new DependencyPropertyChangedEventHandler(layerElement_IsVisibleChanged);
                if (layerElement.IsVisible)
                {
                    layerElement.LayoutUpdated += new EventHandler(layerElement_LayoutUpdated);
                }
                registerIsLayerWithComplexGeometryChangedHandler(layerElement);
                updateRelevantLayerElementsList();
                updateGeometryClip();
            }
        }

        /// <summary>
        /// Removes the given layer element
        /// </summary>
        /// <param name="layerElement">
        /// Layer element
        /// </param>
        internal void RemoveLayerElement(UIElement layerElement)
        {
            if (_layerElements.Contains(layerElement))
            {
                unregisterIsLayerWithComplexGeometryChangedHandler(layerElement);
                layerElement.LayoutUpdated -= new EventHandler(layerElement_LayoutUpdated);
                layerElement.IsVisibleChanged -= new DependencyPropertyChangedEventHandler(layerElement_IsVisibleChanged);
                _layerElements.Remove(layerElement);
                updateRelevantLayerElementsList();
                updateGeometryClip();
            }
        }

        /// <summary>
        /// Handles the "Changed" event of the "StartElement" dependency
        /// property
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void onStartElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            GraphSegmentContainer graphSegmentContainer = sender as GraphSegmentContainer;
            if (graphSegmentContainer != null && args != null)
            {
                UIElement oldElement = args.OldValue as UIElement;
                if (oldElement != null)
                {
                    graphSegmentContainer.unregisterStartNodeDirectionChangedHandler(oldElement);
                    graphSegmentContainer.unregisterStartNodeAlignmentChangedHandler(oldElement);
                    graphSegmentContainer.unregisterGraphNodeLayerIndexChangedHandler(oldElement);
                    oldElement.LayoutUpdated -= new EventHandler(graphSegmentContainer.startElement_LayoutUpdated);
                }
                UIElement newElement = args.NewValue as UIElement;
                if (newElement != null)
                {
                    newElement.LayoutUpdated += new EventHandler(graphSegmentContainer.startElement_LayoutUpdated);
                    graphSegmentContainer.registerGraphNodeLayerIndexChangedHandler(newElement);
                    graphSegmentContainer.registerStartNodeAlignmentChangedHandler(newElement);
                    graphSegmentContainer.registerStartNodeDirectionChangedHandler(newElement);
                }
                graphSegmentContainer.updateStartPoint();
            }
        }

        /// <summary>
        /// Handles the "Changed" event of the "StartElement" dependency
        /// property
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        static private void onEndElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            GraphSegmentContainer graphSegmentContainer = sender as GraphSegmentContainer;
            if (graphSegmentContainer != null && args != null)
            {
                UIElement oldElement = args.OldValue as UIElement;
                if (oldElement != null)
                {
                    graphSegmentContainer.unregisterEndNodeAlignmentChangedHandler(oldElement);
                    oldElement.LayoutUpdated -= new EventHandler(graphSegmentContainer.endElement_LayoutUpdated);
                }
                UIElement newElement = args.NewValue as UIElement;
                if (newElement != null)
                {
                    newElement.LayoutUpdated += new EventHandler(graphSegmentContainer.endElement_LayoutUpdated);
                    graphSegmentContainer.registerEndNodeAlignmentChangedHandler(newElement);
                }
                graphSegmentContainer.updateEndPoint();
            }
        }

        /// <summary>
        /// Registers a graph node layer index changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void registerGraphNodeLayerIndexChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor graphNodeLayerIndexDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.GraphNodeLayerIndexProperty, typeof(GraphSegmentContainer));
            if (graphNodeLayerIndexDescriptor != null)
            {
                graphNodeLayerIndexDescriptor.AddValueChanged(nodeElement, _onGraphNodeLayerIndexChangedEventHandler);
            }
        }

        /// <summary>
        /// Unregisters a graph node layer index changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void unregisterGraphNodeLayerIndexChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor graphNodeLayerIndexDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.GraphNodeLayerIndexProperty, typeof(GraphSegmentContainer));
            if (graphNodeLayerIndexDescriptor != null)
            {
                graphNodeLayerIndexDescriptor.RemoveValueChanged(nodeElement, _onGraphNodeLayerIndexChangedEventHandler);
            }
        }

        /// <summary>
        /// Registers a is layer with complex geometry changed handler for the
        /// given node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void registerIsLayerWithComplexGeometryChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor isLayerWithComplexGeometryDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.IsLayerWithComplexGeometryProperty, typeof(GraphSegmentContainer));
            if (isLayerWithComplexGeometryDescriptor != null)
            {
                isLayerWithComplexGeometryDescriptor.AddValueChanged(nodeElement, _onIsLayerWithComplexGeometryEventHandler);
            }
        }

        /// <summary>
        /// Unregisters a is layer with complex geometry changed handler for the
        /// given node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void unregisterIsLayerWithComplexGeometryChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor isLayerWithComplexGeometryDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.IsLayerWithComplexGeometryProperty, typeof(GraphSegmentContainer));
            if (isLayerWithComplexGeometryDescriptor != null)
            {
                isLayerWithComplexGeometryDescriptor.RemoveValueChanged(nodeElement, _onIsLayerWithComplexGeometryEventHandler);
            }
        }

        /// <summary>
        /// Registers the start node alignment changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void registerStartNodeAlignmentChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor horizontalStartNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.HorizontalStartNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (horizontalStartNodeAlignmentDescriptor != null)
            {
                horizontalStartNodeAlignmentDescriptor.AddValueChanged(nodeElement, _onStartNodeAlignmentChangedEventHandler);
            }
            DependencyPropertyDescriptor verticalStartNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.VerticalStartNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (verticalStartNodeAlignmentDescriptor != null)
            {
                verticalStartNodeAlignmentDescriptor.AddValueChanged(nodeElement, _onStartNodeAlignmentChangedEventHandler);
            }
        }

        /// <summary>
        /// Unregisters the start node alignment changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void unregisterStartNodeAlignmentChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor horizontalStartNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.HorizontalStartNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (horizontalStartNodeAlignmentDescriptor != null)
            {
                horizontalStartNodeAlignmentDescriptor.RemoveValueChanged(nodeElement, _onStartNodeAlignmentChangedEventHandler);
            }
            DependencyPropertyDescriptor verticalStartNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.VerticalStartNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (verticalStartNodeAlignmentDescriptor != null)
            {
                verticalStartNodeAlignmentDescriptor.RemoveValueChanged(nodeElement, _onStartNodeAlignmentChangedEventHandler);
            }
        }

        /// <summary>
        /// Registers the end node alignment changed handler for the given node
        /// element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void registerEndNodeAlignmentChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor horizontalEndNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.HorizontalEndNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (horizontalEndNodeAlignmentDescriptor != null)
            {
                horizontalEndNodeAlignmentDescriptor.AddValueChanged(nodeElement, _onEndNodeAlignmentChangedEventHandler);
            }
            DependencyPropertyDescriptor verticalEndNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.VerticalEndNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (verticalEndNodeAlignmentDescriptor != null)
            {
                verticalEndNodeAlignmentDescriptor.AddValueChanged(nodeElement, _onEndNodeAlignmentChangedEventHandler);
            }
        }

        /// <summary>
        /// Unregisters the end node alignment changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void unregisterEndNodeAlignmentChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor horizontalEndNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.HorizontalEndNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (horizontalEndNodeAlignmentDescriptor != null)
            {
                horizontalEndNodeAlignmentDescriptor.RemoveValueChanged(nodeElement, _onEndNodeAlignmentChangedEventHandler);
            }
            DependencyPropertyDescriptor verticalEndNodeAlignmentDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.VerticalEndNodeAlignmentProperty, typeof(GraphSegmentContainer));
            if (verticalEndNodeAlignmentDescriptor != null)
            {
                verticalEndNodeAlignmentDescriptor.RemoveValueChanged(nodeElement, _onEndNodeAlignmentChangedEventHandler);
            }
        }

        /// <summary>
        /// Registers the start node direction changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void registerStartNodeDirectionChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor startNodeDirectionDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.LineDirectionProperty, typeof(GraphSegmentContainer));
            if (startNodeDirectionDescriptor != null)
            {
                startNodeDirectionDescriptor.AddValueChanged(nodeElement, _onStartNodeDirectionChangedEventHandler);
            }
        }

        /// <summary>
        /// Unregisters the start node direction changed handler for the given
        /// node element
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        private void unregisterStartNodeDirectionChangedHandler(UIElement nodeElement)
        {
            DependencyPropertyDescriptor startNodeDirectionDescriptor = DependencyPropertyDescriptor.FromProperty(GraphContainer.LineDirectionProperty, typeof(GraphSegmentContainer));
            if (startNodeDirectionDescriptor != null)
            {
                startNodeDirectionDescriptor.RemoveValueChanged(nodeElement, _onStartNodeDirectionChangedEventHandler);
            }
        }

        /// <summary>
        /// Handles the "LayoutUpdated" event of the start element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void startElement_LayoutUpdated(object sender, EventArgs args)
        {
            updateStartPoint();
            updateRelevantLayerElementsList();
            updateGeometryClip();
        }

        /// <summary>
        /// Handles the "LayoutUpdated" event of the end element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void endElement_LayoutUpdated(object sender, EventArgs args)
        {
            updateEndPoint();
            updateRelevantLayerElementsList();
            updateGeometryClip();
        }

        /// <summary>
        /// Handles the "LayoutUpdated" event of a layer element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void layerElement_LayoutUpdated(object sender, EventArgs args)
        {
            updateRelevantLayerElementsList();
            updateGeometryClip();
            foreach (LayerElementContainer layerElementContainer in RelevantComplexLayerElements)
            {
                Point offset = getRelativePosition(layerElementContainer.LayerElement as FrameworkElement);
                if (offset.X != layerElementContainer.Margin.Left || offset.Y != layerElementContainer.Margin.Top)
                {
                    layerElementContainer.Margin = new Thickness(offset.X, offset.Y, 0d, 0d);
                }
                layerElementContainer.Height = layerElementContainer.LayerElement.ActualHeight;
                layerElementContainer.Width = layerElementContainer.LayerElement.ActualWidth;
            }
        }

        /// <summary>
        /// Handles the "IsVisibleChanged" event of a layer element
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void layerElement_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            UIElement layerElement = sender as UIElement;
            if (layerElement != null)
            {
                if (layerElement.IsVisible)
                {
                    layerElement.LayoutUpdated += new EventHandler(layerElement_LayoutUpdated);
                }
                else
                {
                    layerElement.LayoutUpdated -= new EventHandler(layerElement_LayoutUpdated);
                }
            }
        }

        /// <summary>
        /// Invalidates the visual
        /// </summary>
        private void invalidateVisual()
        {
            // -->> HACK: to do - implement better solution to refresh (InvalidateVisual does not work!)
            StartPoint = new Point(StartPoint.X + 1, StartPoint.Y);
            StartPoint = new Point(StartPoint.X - 1, StartPoint.Y);
            // <<-- HACK
        }

        /// <summary>
        /// Updates the start point
        /// </summary>
        private void updateStartPoint()
        {
            Point newStartPoint = new Point(0d, 0d);
            if (StartElement != null)
            {
                newStartPoint = getRelativePosition(StartElement as FrameworkElement);
                HorizontalAlignment horizontalStartElementAlignment = GraphContainer.GetHorizontalStartNodeAlignment(StartElement);
                VerticalAlignment verticalStartElementAlignment = GraphContainer.GetVerticalStartNodeAlignment(StartElement);
                if (horizontalStartElementAlignment == HorizontalAlignment.Center)
                {
                    newStartPoint.X += (StartElement as FrameworkElement).ActualWidth / 2d;
                }
                else if (horizontalStartElementAlignment == System.Windows.HorizontalAlignment.Right)
                {
                    newStartPoint.X += (StartElement as FrameworkElement).ActualWidth;
                }
                if (verticalStartElementAlignment == VerticalAlignment.Center)
                {
                    newStartPoint.Y += (StartElement as FrameworkElement).ActualHeight / 2d;
                }
                else if (verticalStartElementAlignment == System.Windows.VerticalAlignment.Bottom)
                {
                    newStartPoint.Y += (StartElement as FrameworkElement).ActualHeight;
                }
            }
            if (newStartPoint.X != StartPoint.X || newStartPoint.Y != StartPoint.Y)
            {
                StartPoint = newStartPoint;
            }
        }

        /// <summary>
        /// Updates the end point
        /// </summary>
        private void updateEndPoint()
        {
            Point newEndPoint = new Point(0d, 0d);
            if (EndElement != null)
            {
                newEndPoint = getRelativePosition(EndElement as FrameworkElement);
                HorizontalAlignment horizontalEndElementAlignment = GraphContainer.GetHorizontalEndNodeAlignment(EndElement);
                VerticalAlignment verticalEndElementAlignment = GraphContainer.GetVerticalEndNodeAlignment(EndElement);
                if (horizontalEndElementAlignment == HorizontalAlignment.Center)
                {
                    newEndPoint.X += (EndElement as FrameworkElement).ActualWidth / 2d;
                }
                else if (horizontalEndElementAlignment == System.Windows.HorizontalAlignment.Right)
                {
                    newEndPoint.X += (EndElement as FrameworkElement).ActualWidth;
                }
                if (verticalEndElementAlignment == VerticalAlignment.Center)
                {
                    newEndPoint.Y += (EndElement as FrameworkElement).ActualHeight / 2d;
                }
                else if (verticalEndElementAlignment == System.Windows.VerticalAlignment.Bottom)
                {
                    newEndPoint.Y += (EndElement as FrameworkElement).ActualHeight;
                }
            }
            if (newEndPoint.X != EndPoint.X || newEndPoint.Y != EndPoint.Y)
            {
                EndPoint = newEndPoint;
            }
        }

        /// <summary>
        /// Updates the relevant layer elements list
        /// </summary>
        private void updateRelevantLayerElementsList()
        {
            List<UIElement> relevantElements = new List<UIElement>();
            Point topLeftLineCorner = new Point(Math.Min(StartPoint.X, EndPoint.X), Math.Min(StartPoint.Y, EndPoint.Y));
            Point bottomRightLineCorner = new Point(Math.Max(StartPoint.X, EndPoint.X), Math.Max(StartPoint.Y, EndPoint.Y));
            int lineStartX = (int)Math.Round(Math.Min(topLeftLineCorner.X, bottomRightLineCorner.X));
            int lineStartY = (int)Math.Round(Math.Min(topLeftLineCorner.Y, bottomRightLineCorner.Y));
            int lineWidth = (int)Math.Round(Math.Abs(bottomRightLineCorner.X - topLeftLineCorner.X));
            int lineHeight = (int)Math.Round(Math.Abs(bottomRightLineCorner.Y - topLeftLineCorner.Y));
            foreach (UIElement layerElement in _layerElements)
            {
                if (!layerElement.IsVisible)
                {
                    continue;
                }
                int startElementLayerIndex = GraphContainer.GetGraphNodeLayerIndex(StartElement);
                int layerElementLayerIndex = GraphContainer.GetLayerIndex(layerElement);
                if (layerElementLayerIndex <= startElementLayerIndex)
                {
                    continue;
                }
                Point topLeftLayerCorner = getRelativePosition(layerElement as FrameworkElement);
                Point bottomRightLayerCorner = new Point(topLeftLayerCorner.X + (layerElement as FrameworkElement).ActualWidth, topLeftLayerCorner.Y + (layerElement as FrameworkElement).ActualHeight);
                int layerStartX = (int)Math.Round(Math.Min(topLeftLayerCorner.X, bottomRightLayerCorner.X));
                int layerStartY = (int)Math.Round(Math.Min(topLeftLayerCorner.Y, bottomRightLayerCorner.Y));
                int layerWidth = (int)Math.Round(Math.Abs(bottomRightLayerCorner.X - topLeftLayerCorner.X));
                int layerHeight = (int)Math.Round(Math.Abs(bottomRightLayerCorner.Y - topLeftLayerCorner.Y));
                if (!System.Drawing.Rectangle.Intersect(new System.Drawing.Rectangle(lineStartX, lineStartY, lineWidth, lineHeight), new System.Drawing.Rectangle(layerStartX, layerStartY, layerWidth, layerHeight)).IsEmpty)
                {
                    relevantElements.Add(layerElement);
                }
            }
            bool changed = (RelevantLayerElements.Count != relevantElements.Count);
            if (!changed)
            {
                foreach (UIElement uiElement in RelevantLayerElements)
                {
                    if (!relevantElements.Contains(uiElement))
                    {
                        changed = true;
                        break;
                    }
                }
            }
            if (changed)
            {
                RelevantLayerElements.Clear();
                RelevantNotComplexLayerElements.Clear();
                RelevantComplexLayerElements.Clear();
                foreach (UIElement layerElement in relevantElements)
                {
                    if (layerElement is FrameworkElement)
                    {
                        Point offset = getRelativePosition(layerElement as FrameworkElement);
                        RelevantLayerElements.Add(layerElement);
                        if (!GraphContainer.GetIsLayerWithComplexGeometry(layerElement))
                        {
                            RelevantNotComplexLayerElements.Add(layerElement);
                        }
                        else
                        {
                            RelevantComplexLayerElements.Add(new LayerElementContainer(layerElement as FrameworkElement, new Thickness(offset.X, offset.Y, 0d, 0d)));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Updates the geometry clip
        /// </summary>
        private void updateGeometryClip()
        {
            if (RelevantNotComplexLayerElements.Count > 0)
            {
                GeometryGroup geometryGroup = new GeometryGroup();
                geometryGroup.FillRule = FillRule.EvenOdd;
                foreach (UIElement layerElement in RelevantNotComplexLayerElements)
                {
                    Point offset = getRelativePosition(layerElement as FrameworkElement);
                    geometryGroup.Children.Add(new RectangleGeometry(new Rect(offset.X, offset.Y, (layerElement as FrameworkElement).ActualWidth, (layerElement as FrameworkElement).ActualHeight)));
                }
                CombinedGeometry combinedGeometry = new CombinedGeometry();
                combinedGeometry.GeometryCombineMode = GeometryCombineMode.Xor;
                double height = (ActualHeight > 0d ? ActualHeight : 1000d);
                double width = (ActualWidth > 0d ? ActualWidth : 1000d);
                combinedGeometry.Geometry1 = new RectangleGeometry(new Rect(0, 0, width, height));
                combinedGeometry.Geometry2 = geometryGroup;
                ClipGeometry = combinedGeometry;
            }
            else
            {
                ClipGeometry = null;
            }
        }

        /// <summary>
        /// Gets the relative position of the given node element to its parent
        /// graph
        /// </summary>
        /// <param name="nodeElement">
        /// Node element
        /// </param>
        /// <returns>
        /// Relative position of the given node element to its parent graph
        /// </returns>
        private Point getRelativePosition(FrameworkElement nodeElement)
        {
            GraphContainer graphContainer = findParent<GraphContainer>(nodeElement);
            if (graphContainer != null)
            {
                return nodeElement.TransformToAncestor(graphContainer).Transform(new Point(0d, 0d));
            }
            return new Point(0d, 0d);
        }

        /// <summary>
        /// Finds the parent element of the given type for the given child
        /// element
        /// </summary>
        /// <typeparam name="T">
        /// Type of the parent to find
        /// </typeparam>
        /// <param name="childElement">
        /// Child element
        /// </param>
        /// <returns>
        /// Parent element or null
        /// </returns>
        static private T findParent<T>(DependencyObject childElement) where T : DependencyObject
        {
            DependencyObject visualParent = VisualTreeHelper.GetParent(childElement);
            if (visualParent == null)
            {
                return null;
            }
            T parent = visualParent as T;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                return findParent<T>(visualParent);
            }
        }

    }

}
